////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//         This file is distributed under the BSD 2-Clause open source license. See Licenses/License.txt for details.         //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include "WNScripting/inc/WNStruct.h"
#include "WNScripting/inc/WNScriptingAllocations.h"
#include "WNScripting/inc/WNCodeModule.h"
#include "WNScripting/inc/WNTypeManager.h"
#include "WNScripting/inc/WNScopedVariableList.h"
#include "WNScripting/inc/WNNodeHelpers.h"
#include "WNScripting/inc/WNDeclaration.h"
#include "WNScripting/inc/WNExpression.h"

#ifdef _WN_MSVC
    #pragma warning(push)
    #pragma warning(disable: 4100)
    #pragma warning(disable: 4127)
    #pragma warning(disable: 4152)
    #pragma warning(disable: 4244)
    #pragma warning(disable: 4245)
    #pragma warning(disable: 4267)
    #pragma warning(disable: 4355)
    #pragma warning(disable: 4512)
#endif

#include "llvm/IR/IRBuilder.h"

#ifdef _WN_MSVC
    #pragma warning(pop)
#endif

using namespace WNScripting; 

WNStruct::WNStruct(const char* _name) {
    COPY_STRING(_name, mName);
}

WNStruct::~WNStruct() {
    WN_DELETE(mName);
    for(WNScriptLinkedList<WNDeclaration>::WNScriptLinkedListNode* i = mStructMembers.first; i != WN_NULL; i=i->next) {
        WN_DELETE(i->value);
    }
}

void WNStruct::AddStructElem(WNDeclaration* _decl) {
    mStructMembers.PushBack(_decl);
}

eWNTypeError WNStruct::ExportNewTypes(WNCodeModule& _module, std::vector<WNScriptType>& _types, std::vector<WNFunctionDefinition*>& _functionDefs, WNLogging::WNLog& _compilationLog) {
    eWNTypeError err = eWNOK;

    WNScriptType scriptType = NULL;
    if(eWNOK != (err = _module.GetTypeManager().RegisterStructType(mName, scriptType))) {
        if(err != eWNAlreadyExists) {
            _compilationLog.Log(WNLogging::eError, 0, "Type cannot be created");
            LogLine(_compilationLog, WNLogging::eError);
            return(err);
        }
    }

    WNScriptType voidReturnType;
    if(eWNOK != (err = _module.GetTypeManager().GetTypeByName("Void", voidReturnType))) {
        _compilationLog.Log(WNLogging::eCritical, 0, "Cannot find Void type");
        LogLine(_compilationLog, WNLogging::eCritical);
        return(err);
    }
    
    std::vector<WNScriptType> parameterTypes;
    parameterTypes.push_back(scriptType);
    WN_SIZE_T nameLen = WNStrings::WNStrLen(mName);
    WN_CHAR *functionName = static_cast<WN_CHAR*>(WN_STACK_ALLOC(sizeof(WN_CHAR) * (nameLen + 1 + 5)));
    WNMemory::WNMemCpy(functionName, mName, nameLen);
    WNMemory::WNMemCpy(functionName + nameLen, "Const", 5);
    functionName[nameLen + 5] = '\0';

    WNFunctionDefinition* constDef = WN_NULL;
    if(eWNOK != (err = _module.GenerateFunctionDefinition(functionName, parameterTypes, voidReturnType, constDef))) {
        parameterTypes.clear();
        _compilationLog.Log(WNLogging::eCritical, 0, "Cannnot create constructor definition");
        LogLine(_compilationLog, WNLogging::eCritical);
        return(err);
    }

    std::vector<llvm::Type*> llvmTypes;
    for(std::vector<WNScriptType>::iterator i = parameterTypes.begin(); i != parameterTypes.end(); ++i) {
        llvmTypes.push_back((*i)->mLLVMType);
    }

    llvm::FunctionType* fType = llvm::FunctionType::get(voidReturnType->mLLVMType, llvmTypes, WN_FALSE);
    constDef->mFunctionType = fType;
    _functionDefs.push_back(constDef);
    //llvm::Function* func = llvm::Function::Create(fType, _external?llvm::GlobalValue::ExternalLinkage:llvm::GlobalValue::InternalLinkage, functionName, _module.GetModule());
    //constDef->mFunction = func;

    WNMemory::WNMemCpy(functionName + nameLen, "Destr", 5);
    WNFunctionDefinition* destrDef = WN_NULL;
    if(eWNOK != (err = _module.GenerateFunctionDefinition(functionName, parameterTypes, voidReturnType, destrDef))) {
        parameterTypes.clear();
        _compilationLog.Log(WNLogging::eCritical, 0, "Cannnot create destructor definition");
        LogLine(_compilationLog, WNLogging::eCritical);
        return(err);
    }

    llvmTypes.clear();
    for(std::vector<WNScriptType>::iterator i = parameterTypes.begin(); i != parameterTypes.end(); ++i) {
        llvmTypes.push_back((*i)->mLLVMType);
    }

    fType = llvm::FunctionType::get(voidReturnType->mLLVMType, llvmTypes, WN_FALSE);
    destrDef->mFunctionType = fType;
    _functionDefs.push_back(destrDef);
    //func = llvm::Function::Create(fType, _external?llvm::GlobalValue::ExternalLinkage:llvm::GlobalValue::InternalLinkage, functionName, _module.GetModule());
    //destrDef->mFunction = func;

    _types.push_back(scriptType);
    return(eWNOK);
}

eWNTypeError WNStruct::FinalizeNewTypes(WNCodeModule& _module, WNLogging::WNLog& _compilationLog) {
    eWNTypeError err = eWNOK;
    WNScriptType scriptType = NULL;
    
    if(eWNOK != (err = _module.GetTypeManager().GetTypeByName(mName, scriptType))) {
        _compilationLog.Log(WNLogging::eError, 0, "Cannot find type ", mName );
        LogLine(_compilationLog, WNLogging::eError);
        return(err);
    }
    if(!scriptType->mLLVMStructType) {
        _compilationLog.Log(WNLogging::eError, 0, "Not a valid struct type ", scriptType->mName );
        LogLine(_compilationLog, WNLogging::eError);
        return(eWNBadType);
    }

    if(eWNOK != (err = FinalizeStruct(_module, scriptType, _compilationLog))) {
        return(err);
    }
    return(eWNOK);
}

eWNTypeError WNStruct::FinalizeStruct(WNCodeModule& _module, WNScriptType scriptType, WNLogging::WNLog& _compilationLog) {
    eWNTypeError err = eWNOK;
    if(scriptType->mFinalizedStruct) {
        return(eWNOK);
    }
    std::vector<llvm::Type*> llvmTypes;

    for(WNScriptLinkedList<WNDeclaration>::WNScriptLinkedListNode* i = mStructMembers.first; i != WN_NULL; i = i->next) {
        for(WNScriptLinkedList<WNDeclaration>::WNScriptLinkedListNode* j = i->next; j != WN_NULL; j = j->next) {
            if(WNStrings::WNStrCmp(i->value->GetName(), j->value->GetName()) == 0){
                _compilationLog.Log(WNLogging::eError, 0, " Member variable already exists ", i->value->GetName());
                j->value->LogLine(_compilationLog, WNLogging::eError);
                return(eWNAlreadyExists);
            }
        }
    }

    for(WNScriptLinkedList<WNDeclaration>::WNScriptLinkedListNode* i = mStructMembers.first; i != WN_NULL; i = i->next) {
        WNScriptType type = WN_NULL;
        if(eWNOK != (err = i->value->GetType(_module, type))) {
            _compilationLog.Log(WNLogging::eError, 0, "Cannot find type" );
            i->value->LogLine(_compilationLog, WNLogging::eError);
            return(err);
        }
        llvmTypes.push_back(type->mLLVMType);
        WNContainedStructType containedType;
        containedType.mName = WNStrings::WNStrNDup(i->value->GetName(), 256);
        containedType.mType = type;
        scriptType->mStructTypes.push_back(containedType);
    }
    
    WNScriptType nullType = NULL;
    if(eWNOK != (err = _module.GetTypeManager().GetTypeByName("-Null", nullType))) {
        return(err);
    }

    WNScriptType boolType = NULL;
    if(eWNOK != (err = _module.GetTypeManager().GetTypeByName("Bool", boolType))) {
        return(err);
    }
    scriptType->mLLVMStructType->setBody(llvmTypes);
    _module.GetTypeManager().RegisterAllocationOperator(scriptType, WN_NEW GenerateDefaultAllocation());
    _module.GetTypeManager().RegisterIDAccessOperator(scriptType, WN_NEW GenerateDefaultStructAccessor(scriptType));
    _module.GetTypeManager().RegisterAssignmentOperator(scriptType, AT_EQ, WN_NEW GenerateStructAssignment());
    _module.GetTypeManager().RegisterAssignmentOperator(scriptType, AT_CHOWN, WN_NEW GenerateStructTransfer());
    _module.GetTypeManager().RegisterDestructionOperator(scriptType, WN_NEW GenerateStructDestruction());
    _module.GetTypeManager().RegisterCastingOperator(nullType, scriptType, WN_NEW GenerateReinterpretCastOperation(scriptType));
    _module.GetTypeManager().RegisterArithmeticOperator(AR_EQ, scriptType, scriptType, WN_NEW GenerateStructCompare(boolType, WN_TRUE));
    _module.GetTypeManager().RegisterArithmeticOperator(AR_NEQ, scriptType, scriptType, WN_NEW GenerateStructCompare(boolType, WN_FALSE));

    scriptType->mFinalizedStruct = true;
    return(eWNOK);
}

eWNTypeError WNStruct::GenerateHeader(WNCodeModule& _module, WNLogging::WNLog& _compilationLog) {
    WNScriptType scriptType = NULL;
    eWNTypeError err = eWNOK;
    if(eWNOK != (err = _module.GetTypeManager().RegisterStructType(mName, scriptType))) {
        if(err != eWNAlreadyExists) {
            _compilationLog.Log(WNLogging::eError, 0, "Type already Exists: ", scriptType->mName);
            LogLine(_compilationLog, WNLogging::eError);
            return(err);
        }
    }

    WNScriptType voidReturnType;
    if(eWNOK != (err = _module.GetTypeManager().GetTypeByName("Void", voidReturnType))) {
        _compilationLog.Log(WNLogging::eCritical, 0, "Cannot find void type: ", scriptType->mName);
        LogLine(_compilationLog, WNLogging::eCritical);
        return(err);
    }
    
    std::vector<WNScriptType> parameterTypes;
    parameterTypes.push_back(scriptType);
    WN_SIZE_T nameLen = WNStrings::WNStrLen(mName);
    WN_CHAR *functionName = static_cast<WN_CHAR*>(WN_STACK_ALLOC(sizeof(WN_CHAR) * (nameLen + 1 + 5)));
    WNMemory::WNMemCpy(functionName, mName, nameLen);
    WNMemory::WNMemCpy(functionName + nameLen, "Const", 5);
    functionName[nameLen + 5] = '\0';

    WNFunctionDefinition* constDef = WN_NULL;
    if(eWNOK != (err = _module.AddFunctionDefinition(functionName, parameterTypes, voidReturnType, constDef))) {
        if(eWNAlreadyExists != err)
        {
            parameterTypes.clear();
            _compilationLog.Log(WNLogging::eError, 0, "Could not create constructor header: ", scriptType->mName);
            LogLine(_compilationLog, WNLogging::eError);
            return(err);
        }
    }

    std::vector<llvm::Type*> llvmTypes;
    for(std::vector<WNScriptType>::iterator i = parameterTypes.begin(); i != parameterTypes.end(); ++i) {
        llvmTypes.push_back((*i)->mLLVMType);
    }

    llvm::FunctionType* fType = llvm::FunctionType::get(voidReturnType->mLLVMType, llvmTypes, WN_FALSE);
    llvm::Function* func = llvm::Function::Create(fType, llvm::GlobalValue::InternalLinkage, functionName, _module.GetModule());
    constDef->mFunction = func;
    constDef->mFunctionType = fType;

    WNMemory::WNMemCpy(functionName + nameLen, "Destr", 5);
    WNFunctionDefinition* destrDef = WN_NULL;
    if(eWNOK != (err = _module.AddFunctionDefinition(functionName, parameterTypes, voidReturnType, destrDef))) {
        if(eWNAlreadyExists != err)
        {
            parameterTypes.clear();
            _compilationLog.Log(WNLogging::eError, 0, "Could not create destructor header: ", scriptType->mName);
            LogLine(_compilationLog, WNLogging::eError);
            return(err);
        }
    }

    llvmTypes.clear();
    for(std::vector<WNScriptType>::iterator i = parameterTypes.begin(); i != parameterTypes.end(); ++i) {
        llvmTypes.push_back((*i)->mLLVMType);
    }

    fType = llvm::FunctionType::get(voidReturnType->mLLVMType, llvmTypes, WN_FALSE);
    func = llvm::Function::Create(fType, llvm::GlobalValue::InternalLinkage, functionName, _module.GetModule());
    destrDef->mFunction = func;
    destrDef->mFunctionType = fType;

    return(eWNOK);
}

eWNTypeError WNStruct::GenerateCode(WNCodeModule& _module, WNLogging::WNLog& _compilationLog) {
    WNScriptType scriptType = NULL;
    eWNTypeError err = eWNOK;

    if(eWNOK != (err = _module.GetTypeManager().GetTypeByName(mName, scriptType))) {
        _compilationLog.Log(WNLogging::eError, 0, "Cannot find type: ", scriptType->mName);
        LogLine(_compilationLog, WNLogging::eError);
        return(err);
    }
    if(!scriptType->mLLVMStructType) {
        _compilationLog.Log(WNLogging::eError, 0, scriptType->mName, " is not a valid Struct type");
        LogLine(_compilationLog, WNLogging::eError);
        return(eWNBadType);
    }

    if(eWNOK != (err = FinalizeStruct(_module, scriptType, _compilationLog))) {
        return(err);
    }

    if(eWNOK != (err = GenerateConstructor(_module, _compilationLog))) {
        return(err);
    }
    if(eWNOK != (err = GenerateDestructor(_module, _compilationLog))) {
        return(err);
    }
    return(eWNOK);
}

eWNTypeError WNStruct::GenerateConstructor(WNCodeModule& _module, WNLogging::WNLog& _compilationLog) {
    llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>*>(_module.GetBuilder());
  
    eWNTypeError err = eWNOK;
    WNScriptType voidReturnType;
    if(eWNOK != (err = _module.GetTypeManager().GetTypeByName("Void", voidReturnType))) {
        return(err);
    }
    WNScriptType scriptType;
    if(eWNOK != (err = _module.GetTypeManager().GetTypeByName(mName, scriptType))) {
        return(err);
    }

    std::vector<WNScriptType> parameterTypes;
    parameterTypes.push_back(scriptType);
    WN_SIZE_T nameLen = WNStrings::WNStrLen(mName);
    WN_CHAR *functionName = static_cast<WN_CHAR*>(WN_STACK_ALLOC(sizeof(WN_CHAR) * (nameLen + 1 + 5)));
    WNMemory::WNMemCpy(functionName, mName, nameLen);
    WNMemory::WNMemCpy(functionName + nameLen, "Const", 5);
    functionName[nameLen + 5] = '\0';
    
    WNFunctionDefinition * def = _module.GetFunctionDefinition(functionName, parameterTypes);
    if(!def) {
        _compilationLog.Log(WNLogging::eError, 0, "No valid constructor for operator for ", scriptType->mName);
        LogLine(_compilationLog, WNLogging::eError);
        return(eWNError);
    }

    llvm::BasicBlock* bb = llvm::BasicBlock::Create(llvm::getGlobalContext(), "", def->mFunction);
    builder->SetInsertPoint(bb);
    llvm::Function::arg_iterator fIter = def->mFunction->arg_begin();
    fIter->setName("this");

    std::vector<llvm::Value*> GepArray;
    GepArray.clear();
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 1));
    llvm::Value* tempValue = builder->CreateLoad(builder->CreateGEP(fIter, GepArray));
    
    WN_SIZE_T cnt = 0;
    for(WNScriptLinkedList<WNDeclaration>::WNScriptLinkedListNode* i = mStructMembers.first; i != WN_NULL; ++cnt, i = i->next) {
        WNScriptType type = WN_NULL;
        if(eWNOK != (err = i->value->GetType(_module, type))) {
            return(err);
        }
        const GenerateAssignment * destOp = _module.GetTypeManager().GetAssignmentOperation(type, AT_EQ);
        if(!destOp) {
            _compilationLog.Log(WNLogging::eError, 0, "No assignment operator for ", type->mName);
            i->value->LogLine(_compilationLog, WNLogging::eError);
            return(eWNInvalidOperation);
        }
        WNExpression* expr = i->value->GetExpression();
        if(!expr) {
            _compilationLog.Log(WNLogging::eError, 0, "Struct members must have a valid initializer");
            i->value->LogLine(_compilationLog, WNLogging::eError);
            return(eWNInvalidOperation);
        }
        if(eWNOK != (err = expr->GenerateCode(_module, def, _compilationLog))) {
            return(err);
        }
        llvm::Value* storeVal = expr->GetValue();
        if(expr->GetType() != type) {
            const GenerateCastingOperation* castOp = _module.GetTypeManager().GetCastingOperation(expr->GetType(), type);
            if(!castOp) {
                _compilationLog.Log(WNLogging::eError, 0, "Cannot cast from: ", expr->GetType()->mName, " to ", type->mName);
                i->value->LogLine(_compilationLog, WNLogging::eError);
                return(eWNInvalidCast);
            }
            if(eWNOK != (err = castOp->Execute(builder, storeVal, storeVal))) {
                return(err);
            }
        }
        
        GepArray.clear();
        GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));
        GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), cnt));
        llvm::Value* storeLocation = builder->CreateGEP(tempValue, GepArray);
        

        if(eWNOK != (err = destOp->Execute(_module, true, type, storeVal, storeLocation, def, _compilationLog))) {
            return(err);
        }
        WNScriptVariable* var = WN_SCRIPTNODE_NEW(WNScriptVariable(type, i->value->GetName(), storeLocation));
        if(eWNOK != (err = _module.GetScopedVariableList().PushVariable(var))) {
            return(err);
        }
    }
    
    _module.GetScopedVariableList().ClearScope(); //Intentionally clear the scope before we return
                                                  //we do not want to clear up anything
    GenerateVOIDReturn(_module, def);
    builder->ClearInsertionPoint();
    return(eWNOK);
}

eWNTypeError WNStruct::GenerateDestructor(WNCodeModule& _module, WNLogging::WNLog& _compilationLog) {
    llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>*>(_module.GetBuilder());
  
    eWNTypeError err = eWNOK;
    WNScriptType voidReturnType;
    if(eWNOK != (err = _module.GetTypeManager().GetTypeByName("Void", voidReturnType))) {
        return(err);
    }
    WNScriptType scriptType;
    if(eWNOK != (err = _module.GetTypeManager().GetTypeByName(mName, scriptType))) {
        return(err);
    }

    std::vector<WNScriptType> parameterTypes;
    parameterTypes.push_back(scriptType);
    WN_SIZE_T nameLen = WNStrings::WNStrLen(mName);
    WN_CHAR *functionName = static_cast<WN_CHAR*>(WN_STACK_ALLOC(sizeof(WN_CHAR) * (nameLen + 1 + 5)));
    WNMemory::WNMemCpy(functionName, mName, nameLen);
    WNMemory::WNMemCpy(functionName + nameLen, "Destr", 5);
    functionName[nameLen + 5] = '\0';
    
    WNFunctionDefinition * def = _module.GetFunctionDefinition(functionName, parameterTypes);
    if(!def) {
        _compilationLog.Log(WNLogging::eError, 0, "Cannot find constructor for struct: ", scriptType->mName);
        LogLine(_compilationLog, WNLogging::eError);
        return(eWNError);
    }
    llvm::BasicBlock* bb = llvm::BasicBlock::Create(llvm::getGlobalContext(), "", def->mFunction);
    builder->SetInsertPoint(bb);
    llvm::Function::arg_iterator fIter = def->mFunction->arg_begin();
    fIter->setName("this");

    std::vector<llvm::Value*> GepArray;
    GepArray.clear();
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));
    GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 1));
    llvm::Value* tempValue = builder->CreateLoad(builder->CreateGEP(fIter, GepArray));

    WN_SIZE_T cnt = 0;
    for(WNScriptLinkedList<WNDeclaration>::WNScriptLinkedListNode* i = mStructMembers.first; i != WN_NULL; ++cnt, i = i->next) {
        WNScriptType type = WN_NULL;
        if(eWNOK != (err = i->value->GetType(_module, type))) {
            return(err);
        }
        const GenerateDestruction* destOp = _module.GetTypeManager().GetDestructionOperation(type);
        if(destOp) {
            GepArray.clear();
            GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));
            GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), cnt));
            llvm::Value* valLoc = builder->CreateGEP(tempValue, GepArray);

            eWNTypeError err = eWNOK;
            if(eWNOK != (err = destOp->Execute(_module, type, valLoc, def, _compilationLog))){
                return(err);
            }
        }
    }

    GenerateVOIDReturn(_module, def);
    _module.GetScopedVariableList().ClearScope();
    builder->ClearInsertionPoint();
    return(eWNOK);
}

